Skip to content

[jaxrs-spec][quarkus] Emit @RolesAllowed({"**"}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes and rename "useQuarkusSecurityAnnotations" to "useJakartaSecurityAnnotations"#23680

Open
Ignacio-Vidal wants to merge 17 commits intoOpenAPITools:masterfrom
Ignacio-Vidal:quarkus-authentication

Conversation

@Ignacio-Vidal
Copy link
Copy Markdown
Contributor

@Ignacio-Vidal Ignacio-Vidal commented May 3, 2026

This is part 2 of #23691 to improve authentication and authorisation support in the jaxrs-spec/quarkus server generator.

What this PR does

When the new useJakartaSecurityAnnotations flag is enabled (Quarkus library only, requires useJakartaEe=true), the generator emits @jakarta.annotation.security.RolesAllowed({"**"}) on JAX-RS interface methods and implementation stubs for operations whose security requirements mean any authenticated user is sufficient — i.e. authentication is required but no specific role or scope is enforced.

Configuration

Property Default Requires
useJakartaSecurityAnnotations false library=quarkus, useJakartaEe=true

Setting useJakartaSecurityAnnotations=true with useJakartaEe=false fails fast with IllegalArgumentException@jakarta.annotation.security.RolesAllowed cannot coexist with the javax.* namespace.

Semantic rules

Schemes that qualify on their own

Scheme type Condition
HTTP Basic / Bearer Always — no scope concept
API Key Always — no scope concept
MutualTLS Always — no scope concept; mTLS produces an authenticated SecurityIdentity in Quarkus
OAuth2 / OpenID Connect Only when the operation's scope list is empty (scheme: [])

OR semantics — at least one alternative must qualify

The OpenAPI security array is OR: a request is authorised if any one of the listed alternatives is satisfied. The annotation is emitted when at least one OR alternative fully qualifies on its own:

security:
  - oauth2_read: [read:items]   # scoped — does not qualify alone
  - oauth2_admin: []            # empty scopes — qualifies → annotation emitted

AND semantics — all co-required schemes must qualify

A single SecurityRequirement object with multiple keys is AND: all listed schemes must be satisfied simultaneously. If any scheme in the AND group carries explicit scopes, the combined requirement is more restrictive than "any authenticated user", so the annotation is not emitted:

security:
  - oauth2Scheme: []            # would qualify alone…
    openIdScheme: [admin:create] # …but AND'd with a scoped scheme → not emitted

Anonymous OR alternative (- {}) — least restrictive wins

OpenAPI 3 allows an empty SecurityRequirement (- {}) inside the OR list to indicate anonymous access is also acceptable. When present, the least-restrictive alternative is "no auth required", so emitting @RolesAllowed({"**"}) (which forces authentication) would contradict the spec. The annotation is not emitted in this case:

security:
  - oauth2: []
  - {}                         # anonymous allowed → no @RolesAllowed emitted

(@PermitAll for this case will land in the next PR.)

Global vs per-operation security

Operations with no security field inherit openapi.security. Operations with an explicit empty list (security: []) opt out and produce no annotation, regardless of the global block.

Why the annotation is not emitted when scopes are present

@RolesAllowed({"**"}) means any authenticated principal. If scopes are present, the intent is to restrict to principals holding a specific role. Emitting @RolesAllowed({"**"}) in that case would be too permissive and contradict the spec. Those operations are left unannotated pending a follow-up PR that will emit @RolesAllowed({scope}) for the all-scoped case.

Emitting both annotations on the same method is not a valid fallback: Quarkus applies them with AND semantics, making @RolesAllowed({"**"}) redundant at best and silently incorrect at worst.

Implementation notes

Dedicated processor class. The security gate logic lives in JakartaSecurityAnnotationProcessor (package-private, in org.openapitools.codegen.languages). JavaJAXRSSpecServerCodegen.fromOperation delegates to it via processor.applyTo(op, rawOperation, openAPI). This keeps the codegen class small and lets future PRs (@RolesAllowed({scope}), @PermitAll) add new branches without further bloating it.

Why fromOperation and not postProcessOperationsWithModels. The OpenAPI security array uses a List<SecurityRequirement> where each element is a map of scheme name → scope list. A single map entry with multiple keys is an AND group. By the time postProcessOperationsWithModels runs, DefaultGenerator.filterAuthMethods has already flattened all SecurityRequirement objects into a plain List<CodegenSecurity>, discarding which schemes were co-located in the same AND group. Evaluating mixed-scope AND groups correctly requires the raw structure, which is still available in fromOperation via the Operation parameter.

processOpts ordering. The flag is read after super.processOpts() so that consumers who supply library via Gradle's configOptions (rather than as a direct task property) are honoured — the library field is null until super resolves it from additionalProperties.

Vendor extension. The processor sets x-jakarta-any-roles = true on qualifying operations. A shared partial jakartaSecurityAnnotations.mustache is included from both apiInterface.mustache (interface-only mode) and apiMethod.mustache (implementation stub mode), so the annotation lives in one place.

Defensive logging. Spec references to undefined schemes, schemes with missing type, or unrecognised scheme types produce a LOGGER.warn so users can diagnose generated output that lacks expected annotations.

Test coverage

A single parameterised test (quarkusEmitsExpectedRolesAllowedWildcardCount) drives a 32-row @DataProvider covering:

Scenario Fixture
OAuth2 — empty scopes / explicit scopes / multi-flow quarkus-oauth2-no-scopes.yaml, quarkus-oauth2-with-scopes.yaml, quarkus-oauth2-multi-flow-no-scopes.yaml
OAuth2 OR — unscoped + scoped, scoped + API Key quarkus-oauth2-or-empty-and-scoped.yaml, quarkus-oauth2-scoped-or-api-key.yaml
HTTP Basic / Bearer / API Key quarkus-http-basic.yaml, quarkus-http-bearer.yaml, quarkus-api-key.yaml
OpenID Connect — empty / scoped quarkus-openidconnect-no-scopes.yaml, quarkus-openidconnect-with-scopes.yaml
Global security inherit / per-op security: [] opt-out quarkus-global-security-one-op-disabled.yaml, quarkus-global-oauth2-or-scoped-and-unscoped.yaml
AND group with mixed scopes quarkus-and-group-mixed-scopes.yaml
OR list with anonymous alternative (- {}) quarkus-or-with-anonymous.yaml

Plus targeted tests:

  • quarkusJakartaSecurityCoexistsWithMicroProfileAnnotations — asserts @RolesAllowed and MicroProfile @SecurityRequirement both render on the same method (quarkus-microprofile-coexist.yaml).
  • useJakartaSecurityIsHonouredWhenLibrarySuppliedViaAdditionalProperties — regression for the Gradle configOptions library-resolution path.
  • useJakartaSecurityRequiresUseJakartaEe — fail-fast guard.

Migration

Replace useQuarkusSecurityAnnotations with useJakartaSecurityAnnotations so the support for authentication and authorisation annotations can be extended to other libraries under the same generator. #23699 added the initial useQuarkusSecurityAnnotations annotation to master and has not been released in a tag version yet.

Generated code now uses @jakarta.annotation.security.RolesAllowed({"**"}) instead of @io.quarkus.security.Authenticated.

PR checklist

  • Read the contribution guidelines.
  • Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
  • Run the following to build the project and update samples:
    ./mvnw clean package || exit
    ./bin/generate-samples.sh ./bin/configs/*.yaml || exit
    ./bin/utils/export_docs_generators.sh || exit
    
    (For Windows users, please run the script in WSL)
  • File the PR against the correct branch: master (upcoming 7.x.0 minor release).
  • If your PR solves a reported issue, reference it using GitHub's linking syntax (e.g., having "fixes #123" present in the PR description).
  • If your PR is targeting a particular programming language, @mention the technical committee members, so they are more likely to review the pull request.

@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @Authenticated for OAuth2 security schemes without scopes DRAFT: [jaxrs-spec][quarkus] Emit @Authenticated for OAuth2 security schemes with empty scopes array May 4, 2026
@Ignacio-Vidal Ignacio-Vidal force-pushed the quarkus-authentication branch 3 times, most recently from 7479247 to 5daad84 Compare May 5, 2026 21:31
@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @Authenticated for OAuth2 security schemes with empty scopes array DRAFT: [jaxrs-spec][quarkus] Emit @Authenticated for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes array May 5, 2026
@Ignacio-Vidal Ignacio-Vidal force-pushed the quarkus-authentication branch from 5daad84 to 9b2aa6e Compare May 6, 2026 22:12
@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @Authenticated for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes array DRAFT: [jaxrs-spec][quarkus] Emit @Authenticated for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes May 6, 2026
@Ignacio-Vidal Ignacio-Vidal marked this pull request as ready for review May 7, 2026 07:07
@Ignacio-Vidal Ignacio-Vidal marked this pull request as draft May 7, 2026 07:08
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 17 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="quarkus-security-github-issue.md">

<violation number="1" location="quarkus-security-github-issue.md:59">
P1: OpenAPI scope requirements are conjunctive, but this mapping uses `@RolesAllowed` OR semantics and can under-enforce multi-scope authorization.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread quarkus-security-github-issue.md
@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @Authenticated for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({**})d for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes May 8, 2026
@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({**})d for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({**}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes May 8, 2026
@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({**}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({"**"}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes May 8, 2026
@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({"**"}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({"**"}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes and rename "useQuarkusSecurityAnnotations" to "useJakartaSecurityAnnotations" May 8, 2026
@Ignacio-Vidal Ignacio-Vidal force-pushed the quarkus-authentication branch from e92ab2e to b3b43db Compare May 8, 2026 21:30
@Ignacio-Vidal Ignacio-Vidal marked this pull request as ready for review May 8, 2026 23:40
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 20 files

@Ignacio-Vidal Ignacio-Vidal changed the title DRAFT: [jaxrs-spec][quarkus] Emit @RolesAllowed({"**"}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes and rename "useQuarkusSecurityAnnotations" to "useJakartaSecurityAnnotations" [jaxrs-spec][quarkus] Emit @RolesAllowed({"**"}) for HTTP Basic, Bearer, api-key and OAuth2 or OpenID with empty scopes and rename "useQuarkusSecurityAnnotations" to "useJakartaSecurityAnnotations" May 8, 2026
@Ignacio-Vidal Ignacio-Vidal force-pushed the quarkus-authentication branch 2 times, most recently from e9baa9a to 9e7f29b Compare May 9, 2026 09:00
@Ignacio-Vidal
Copy link
Copy Markdown
Contributor Author

Ignacio-Vidal commented May 9, 2026

@wing328 - any chance you could review this?

I have tested this in a separate projec that loads the openapi-generator-SNAPSHOT version from my local m2:

  • See the api spec with all cases covered in this MR
  • See the adapter implementing the generated interface
  • See the http.requests using intellij client
  • See the integration tests using the generated client with microprofile library
image

@Ignacio-Vidal Ignacio-Vidal force-pushed the quarkus-authentication branch from e2b3964 to 1b06135 Compare May 9, 2026 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant